﻿using System;
using System.Collections.Generic;
using System.Runtime.ExceptionServices;
using Newtonsoft.Json;

namespace Hsenl {
    [Serializable]
    public class DBDocument {
        [JsonProperty]
        private List<DBField> _fields = new();

        [JsonIgnore]
        private Dictionary<string, DBField> _fieldsDict;

        private void MakeSureCacheDict() {
            if (this._fieldsDict == null)
                this._fieldsDict = new Dictionary<string, DBField>();

            if (this._fields.Count == this._fieldsDict.Count)
                return;

            this._fieldsDict.Clear();
            ExceptionDispatchInfo exception = null;
            foreach (var field in this._fields) {
                try {
                    this._fieldsDict.Add(field.FieldName, field);
                }
                catch (Exception e) {
                    if (exception == null) {
                        exception = ExceptionDispatchInfo.Capture(e);
                    }
                }
            }

            exception?.Throw();
        }

        private bool ContainsField(string fieldName) {
            return this._fieldsDict.ContainsKey(fieldName);
        }

        public void InsertOne<T>(string fieldName, T fieldValue) {
            this.MakeSureCacheDict();
            if (this.ContainsField(fieldName))
                throw new ArgumentException($"Field {fieldName} already exists");

            var field = new DBField<T>(fieldName, fieldValue);

            this.InsertOne_Internal(field);
        }

        public void InsertOne(DBField field) {
            var fieldName = field.FieldName;
            this.MakeSureCacheDict();
            if (this.ContainsField(fieldName))
                throw new ArgumentException($"Field {fieldName} already exists");

            this.InsertOne_Internal(field);
        }

        private void InsertOne_Internal(DBField field) {
            this._fields.Add(field);
            this._fieldsDict.Add(field.FieldName, field);
        }

        public void InsertMany(ICollection<DBField> fields) {
            foreach (var field in fields) {
                this.InsertOne(field);
            }
        }

        public void InsertMany(IList<DBField> fields) {
            for (int i = 0; i < fields.Count; i++) {
                var field = fields[i];
                this.InsertOne(field);
            }
        }

        public bool RemoveOne(string fieldName) {
            var b = false;
            for (int i = 0; i < this._fields.Count; i++) {
                var field = this._fields[i];
                if (field.FieldName == fieldName) {
                    this._fields.RemoveAt(i);
                    b = true;
                    break;
                }
            }

            this._fieldsDict.Remove(fieldName);

            return b;
        }

        public void RemoveAll() {
            this._fields.Clear();
            this._fieldsDict.Clear();
        }

        public T GetValue<T>(string fieldName, T defaultValue = default) {
            this.MakeSureCacheDict();
            if (this._fieldsDict.TryGetValue(fieldName, out var field)) {
                return field.GetValue<T>();
            }

            return defaultValue;
        }

        public void SetValue<T>(string fieldName, T value) {
            this.MakeSureCacheDict();
            if (this._fieldsDict.TryGetValue(fieldName, out var field)) {
                field.SetValue(value);
            }
            else {
                this.InsertOne(fieldName, value);
            }
        }
    }
}